#include <utils/list.h>
#include <seminix/idtable.h>
#include <seminix/param.h>
#include <seminix/cpu.h>
#include <seminix/smp.h>
#include <seminix/mm.h>
#include <seminix/tcb.h>
#include <seminix/init.h>
#include <seminix/spinlock.h>
#include <seminix/slab.h>

DEFINE_SPINLOCK(tcb_list_lock);
LIST_HEAD(tcb_list);

static DEFINE_SPINLOCK(tid_lock);
static struct idtable *tid_bits;

static struct kmem_cache *tcb_cachep;
static struct kmem_cache *tcb_stack_cachep;

static void tcb_list_insert(struct tcb *tsk)
{
    spin_lock(&tcb_list_lock);
    list_add(&tsk->tcb_node, &tcb_list);
    spin_unlock(&tcb_list_lock);
}

static void tcb_list_remove(struct tcb *tsk)
{
    spin_lock(&tcb_list_lock);
    list_del(&tsk->tcb_node);
    spin_unlock(&tcb_list_lock);
}

static int alloc_tid_idtable(void)
{
    int id;

    spin_lock(&tid_lock);
    id = id_table_get_free(tid_bits, 0);
    if (id < 0)
        return -ENOMEM;
    spin_unlock(&tid_lock);
    return id;
}

static void free_tid_idtable(int tid)
{
    spin_lock(&tid_lock);
    id_table_put(tid_bits, tid);
    spin_unlock(&tid_lock);
}

static inline struct tcb *alloc_tcb_struct(void)
{
    return kmem_cache_alloc(tcb_cachep, GFP_KERNEL | GFP_ZERO);
}

static inline void free_tcb_struct(struct tcb *tsk)
{
    kmem_cache_free(tcb_cachep, tsk);
}

static inline void *alloc_tcb_stack(void)
{
    return kmem_cache_alloc(tcb_stack_cachep, GFP_KERNEL);
}

static inline void free_tcb_stack(void *stack)
{
    kmem_cache_free(tcb_stack_cachep, stack);
}

static void tcb_struct_init(struct tcb *tsk)
{
    atomic_set(&tsk->usage, 1);
    raw_spin_lock_init(&tsk->pi_lock);

    set_task_stack_end_magic(tsk);
    atomic_set(&tsk->stack_refcount, 1);

    spin_lock_init(&tsk->sighand.siglock);

    tsk->restart_block.fn = do_no_restart_syscall;

    init_user_thread(&tsk->thread);
}

struct tcb *tcb_struct_create(void)
{
    struct tcb *tsk;

    tsk = alloc_tcb_struct();
    if (!tsk)
        goto out;

    tsk->stack = alloc_tcb_stack();
    if (!tsk->stack)
        goto free_tsk;

    tsk->tid = alloc_tid_idtable();
    if (tsk->tid < 0)
        goto free_tsk_tcb;

    init_user_thread_info(&tsk->thread_info);
    tcb_struct_init(tsk);
    tcb_list_insert(tsk);

    return tsk;
free_tsk_tcb:
    free_tcb_stack(tsk->stack);
free_tsk:
    free_tcb_struct(tsk);
out:
    return NULL;
}

void __put_task_struct(struct tcb *tsk)
{
    WARN_ON(atomic_read(&tsk->usage));
    WARN_ON(tsk == current);
    WARN_ON(tsk->mm);

    tcb_list_remove(tsk);
    free_tid_idtable(tsk->tid);
    put_task_stack(tsk);
    free_tcb_struct(tsk);
}

void put_task_stack(struct tcb *tsk)
{
    if (atomic_dec_and_test(&tsk->stack_refcount))
        free_tcb_stack(tsk->stack);
}

/*
 *  'fork.c' contains the help-routines for the 'fork' system call
 * (see also entry.S and others).
 * Fork is rather simple, once you get the hang of it, but the memory
 * management can be a bitch. See 'mm/memory.c': 'copy_page_range()'
 */

void set_task_stack_end_magic(struct tcb *tsk)
{
    unsigned long *stackend;

    stackend = end_of_stack(tsk);
    *stackend = STACK_END_MAGIC;	/* for overflow detection */
}

struct tcb * __init idle_task_init_once(int cpu)
{
    struct tcb *idle;

    if (cpu == boot_cpu_id()) {
        idle = current;
        goto out;
    }

    idle = kzalloc(sizeof (*idle), GFP_KERNEL);
    if (!idle)
        goto out1;
    idle->stack = kmalloc(THREAD_SIZE, GFP_KERNEL);
    if (!idle->stack)
        goto free_idle;
    idle->tid = -cpu;
    init_idle_thread_info(&idle->thread_info);
    tcb_struct_init(idle);
out:
    mm_set_task(&init_mm, idle);
    tcb_list_insert(idle);
    init_idle(idle, cpu);
    return idle;
free_idle:
    kfree(idle);
out1:
    return NULL;
}

static int __init tcb_and_idtable_init(void)
{
    tid_bits = id_table_create(NR_TCB_DEFAULT, 0);
    if (!tid_bits)
        panic("failed to tid table init!\n");
    if (id_table_get_free(tid_bits, 0))
        panic("tid first id none zero!\n");

    tcb_cachep = KMEM_CACHE(tcb, SLAB_PANIC);
    tcb_stack_cachep = kmem_cache_create("tcb_stack", THREAD_SIZE, THREAD_SIZE, SLAB_PANIC, NULL);

    return 0;
}
core_initcall(tcb_and_idtable_init)
